from engine import Task,ThreadedTask,Runner,EventHandler
from sdlengine import Period,load_font,Period,AnimationFromGenerator,SDLEngine as Engine,load_trans_image,transparent
from threading import Thread
from integrate import load_dict
import pygame,os,json
from data import Cache
from math import floor
import random;random.seed()

GLOBAL_DEBUG=True
#GLOBAL_DEBUG=False

def combine(a,b,c):return [c(a[i],b[i]) for i in range(0,len(a))]

def sizedrangepoint(l,m,p):return l[p+1-m:p+1] if p+1>=m else l[:p+1]

def formula(r,a):
	for i in r:yield a(i)

def quit_on_backspace(o):return (lambda ev:o.stop() if ev.key==pygame.K_BACKSPACE else None)

def trykey(o,k,d):
	try:return o[k]
	except (KeyError,IndexError):return d

def trykeys(o,*k,d=None):
	for key in k:
		try:return o[key]
		except(KeyError,IndexError):pass
	return d

def listdir(path):
		return [ fn for v in [[ os.path.join(*b) for b in [ (a[0], c) for c in a[2]]] for a in [ [r,d,f] for r,d,f in os.walk(path)]] for fn in v]

def fn_tokey(fn):return fn[:fn.rfind('.')].lower()

def ensure_list(a):return a if hasattr(a,'__len__') else [a]

def symmetrical(a):
	r=[i for i in a]
	return r+r[::-1]

def padding(container,padding,position):
	inner,outer=combine(container,padding,lambda a,b:a-(2*b)),container
	return (position+outer),(combine(position,padding,lambda a,b:a+b)+inner)

def link_tasks(o,*t):
	for task in t:
		o.connect_signal('start',task.start)
		o.connect_signal('stop',task.stop)

class OwnedTimer(Task):

	def __init__(self,owner,rate,engine=None):
		Task.__init__(self,engine or owner.engine)
		self.timer=Period(rate)
		self.connect_signal('start',self.timer.reset)
		self.connect_signal('post-elapse',self.timer.reset)
		self.connect_signal('frame',lambda:self.elapse() if self.timer.elapsed else None)
		link_tasks(owner,self)

	@EventHandler.Signal('elapse')
	def elapse(self):self.timer.reset()

class FlowControl(Task):

	def __init__(self,engine,end,*intros):
		Task.__init__(self,engine)
		self.connect_signal('start',intros[0].start)
		for i in range(0,len(intros)):
			intros[i].connect_signal('stop',trykey(intros,i+1,end).start)
		end.connect_signal('start',self.stop)

class MultiLauncher(EventHandler):

	def __init__(self,*tasks):
		EventHandler.__init__(self)
		self.connect_signal('start',*[t.start for t in tasks])
		self.connect_signal('start',self.stop)

	@property
	def running(self):return False

class ScreenItem(pygame.Surface,Task):

	def __init__(self,engine,dimensions,flags=0,position=[0,0],frame=None,bf=0):
		Task.__init__(self,engine)
		pygame.Surface.__init__(self,dimensions,flags)
		self.blit_flags,self.blit_frame,self.blit_position=bf,frame,position
		self.connect_signal('frame',self.paint)
		self.connect_signal('start',self.update)
		self.fill((255,0,255))
		transparent(self)

	@EventHandler.Signal('paint')
	def update(self):pass

	def paint(self):self.engine.surf.blit(self,self.blit_position,self.blit_frame,self.blit_flags)

class ScreenItemCopy(ScreenItem):

	def __init__(self,engine,surf,size=None,position=[0,0],frame=None,bf=0):
		ScreenItem.__init__(self,engine,size or surf.get_size(),surf.get_flags(),position,frame,bf)
		self.connect_signal('paint',lambda:self.blit(self.__source_surface__,(0,0)))
		self.__source_surface__=surf

class TextBox(ScreenItem):

	main_line=0
	lines_displayed=3
	padding=[10,5]

	def __init__(self,engine,text):
		font=engine.textbox_font
		screen=engine.resolution[:]
		total_size=[screen[0],font.char_height*self.lines_displayed+self.padding[1]*2]
		ScreenItem.__init__(self,engine,total_size,position=(0,screen[1]-total_size[1]))
		self.text_area=combine(self.padding+total_size,[0,0]+self.padding,lambda a,b:a-(2*b))
		self.text_lines=font.render_lines(font.trim_width(self.text_area[2]),text)
		self.width_step=None
		self.connect_signal('paint',lambda:self.fill([255]*3),lambda:self.fill([0]*3,self.text_area))
		self.__revealed_area__=[0,0,0,font.char_height]
		self.connect_signal('start',self.reset_position)
		self.connect_signal('frame',self.paint_text)
		self.connect_signal(pygame.KEYDOWN,self.on_keydown)

	@property
	def is_done(self):
		return self.main_line>=len(self.text_lines)

	@property
	def active_lines(self):
		return sizedrangepoint(self.text_lines,self.lines_displayed,self.main_line)

	@property
	def waiting_for_key(self):
		if self.is_done:return True
		return self.width_step.complete

	def reset_position(self):
		self.main_line=-1
		self.advance_line()

	@EventHandler.Signal('advance-line')
	def advance_line(self):
		self.__revealed_area__[2]=0
		self.main_line+=1
		if self.main_line>=len(self.text_lines):
			self.stop()
		else:
			self.width_step=AnimationFromGenerator(10,range(0,self.text_lines[self.main_line].get_width(),2))
			self.width_step.accelerate_rate=4

	def line_area(self,line):
		if line is self.text_lines[self.main_line]:
			self.__revealed_area__[2]=self.width_step.value
			return self.__revealed_area__
		return [0,0]+[line.get_width(),line.get_height()]

	def on_keydown(self,event):
		if self.waiting_for_key and event.key==pygame.K_RETURN:
			self.advance_line()
		elif event.key==pygame.K_SPACE:
			self.width_step.accelerate=True

	def paint_text(self):
		pos=self.text_area[:2]
		for line,rect in [(surf,self.line_area(surf)) for surf in self.active_lines]:
			self.blit(line,pos,rect)
			pos[1]+=line.get_height()

class Menu(EventHandler):

	def __init__(self,items):
		EventHandler.__init__(self)
		self.__pos__,self.items,self.value=0,tuple(items),None
		self.connect_signal('select',self.stop)
		self.connect_signal('start',self.reset)

	def reset(self):self.value=None

	@EventHandler.Signal('select')
	def select(self):self.value=self.items[self.position]

	@EventHandler.Signal('go-up')
	def go_up(self):self.position-=1

	@EventHandler.Signal('go-down')
	def go_down(self):self.position+=1

	@property
	def running(self):return self.value is not None

	@property
	def position(self):return self.__pos__

	@position.setter
	@EventHandler.Signal('position-change')
	def position(self,value):
		self.__pos__=value
		il=len(self.items)
		while self.__pos__>=il:self.__pos__-=il
		while self.__pos__<0:self.__pos__+=il

class TrainerCardView(ScreenItem):

	def __init__(self,engine,card):
		self.card=card
		ScreenItem.__init__(self,engine,engine.resolution)
		self.connect_signal(pygame.KEYDOWN,quit_on_backspace(self))
		self.connect_signal('paint',lambda:self.fill((200,200,225)))
		self.connect_signal('post-paint',self.paint_fields)
	def paint_fields(self):
		card=self.card
		font=self.engine.main_font
		sprite='trainer::'+card['class'].lower()
		sprite=trykeys(self.engine.sprites,sprite,sprite+"::"+card['gender'])
		self.blit(sprite,(0,0))
		field_width=300
		field_pos=[sprite.get_width(),0]
		fields=[
			card['name'],
			"Class: "+card['class'].title(),
			"Pokedex: "+str(len(card['pokedex'])),
			"Money: $"+str(card['money'])
		]
		for field in fields:
			surf=font.render_lines(font.trim_width(field_width),field)[0]
			self.blit(surf,field_pos)
			field_pos[1]+=surf.get_height()+5#5=padding between fields.
		emblem_pos,emblem_padding=[0,sprite.get_height()],8
		for emblem in ["rosario"]*20 if GLOBAL_DEBUG else card['emblems']:
			surf=self.engine.sprites['emblems::'+emblem]
			self.blit(surf,emblem_pos)
			emblem_pos[0]+=surf.get_width()+emblem_padding
			if emblem_pos[0]+surf.get_width()>=self.get_width():
				emblem_pos=[0,emblem_pos[1]+surf.get_height()+emblem_padding]

class BagView(ScreenItem):

	def __init__(self,engine,card):
		ScreenItem.__init__(self,engine,engine.resolution)
		font=engine.main_font
		field_width=font.trim_width(200)
		def create_item(name,quantity):
			return {
				'name':name,
				'quantity':quantity,
				'icon':trykey(engine.sprites,'item::'+name,engine.sprites['item::none']),
				'text':font.render_lines(field_width,name+" x"+str(quantity))[0]
			}
		items=[create_item(name,card['bag'][name]) for name in card['bag']]
		self.menu=Menu(items)
		self.connect_signal("post-paint",self.paint_items)
		self.connect_signal(pygame.KEYDOWN,quit_on_backspace(self))

	@property
	def viewable_items(self):return self.menu.items

	def paint_items(self):
		pos=[0,0]
		for item in self.viewable_items:
			ih,th=item['icon'].get_height(),item['text'].get_height()
			if ih>th:
				self.blit(item['icon'],pos)
				self.blit(item['text'],(pos[0]+item['icon'].get_width(),pos[1]+(ih-th)//2))
				pos[1]+=ih
			else:
				self.blit(item['icon'],(pos[0],(th-ih)//2))
				self.blit(item['text'],(pos[0]+item['icon'].get_width(),pos[1]))
				pos[1]+=th

class RosterView(ScreenItemCopy):

	def __init__(self,engine,trainer_card):
		ScreenItemCopy.__init__(self,engine,engine.pictures['rosterviewbg'])
		self.card=trainer_card
		self.party=[trainer_card['storage'][id] for id in trainer_card['roster']]
		def get_name(d):
			return trykey(d,'name',False) or trykey(d,'species','???')
		self.menu=Menu([get_name(pkmn) for pkmn in self.party])
		self.active_bg,self.inactive_bg=self.engine.pictures['rosterviewhl'],self.engine.pictures['rosterviewpk']
		self.cursor=self.engine.pictures['ball_cursor']
		transparent(self.active_bg)
		transparent(self.inactive_bg)
		font=self.engine.main_font
		def create_item(name):
			def get_pk(n):
				for pk in self.party:
					if get_name(pk)==n:return pk
			pk=get_pk(name)
			r={'icon':None,'text':font.render_lines(font.trim_width(self.active_bg.get_width()),name)[0],'pokemon':pk}
			ico_key=trykey(pk,'species','???')
			if 'form' in pk:ico_key+='::'+pk['form']
			r['icon']=self.engine.sprites['pokemon::icons::'+ico_key]
			return r
		self.items=[create_item(name) for name in self.menu.items]
		self.menu.connect_signal('post-position-change',lambda val:self.update())
		self.connect_signal(pygame.KEYDOWN,self.move)
		self.connect_signal('post-paint',self.paint_pokemon,self.paint_cursor)

	def move(self,event):
		if event.key==pygame.K_UP:self.menu.go_up()
		elif event.key==pygame.K_RIGHT:self.menu.position+=3
		elif event.key==pygame.K_DOWN:self.menu.go_down()
		elif event.key==pygame.K_LEFT:self.menu.position-=3
		elif event.key==pygame.K_RETURN:self.menu.select()
		elif event.key==pygame.K_BACKSPACE:self.stop()

	def paint_cursor(self):
		def paint_item(item,p):
			padding=[11,11]
			status='ball_stats::'+((trykey(item['pokemon'],'status',None) or 'normal') if item else 'none')
			if item==self.items[self.menu.position]:
				self.blit(self.cursor,p)
			self.blit(self.engine.graphics[status],combine(p,padding,lambda a,b:a+b))
			p[0]+=self.cursor.get_width()
			return p
		pos=[20,270]
		for item in self.items[:]+([None]*(6-len(self.items))):
			pos=paint_item(item,pos)

	def paint_pokemon(self):
		item_padding=[14,8]
		icon_pos=[10,20]
		key_point=238
		def paint_item(item,bg,p):
			self.blit(bg,p)
			if item:
				ico_pos=combine(p,icon_pos,lambda a,b:a+b)
				self.blit(item['icon'],ico_pos)
				self.blit(item['text'],combine(ico_pos,[item['icon'].get_width()+5,0],lambda a,b:a+b))
			p[1]+=self.inactive_bg.get_height()+item_padding[1]
			if p[1]+self.inactive_bg.get_height()>=key_point:
				p[0]+=self.inactive_bg.get_width()+item_padding[0]
				p[1]=self.blit_position[1]+item_padding[1]*2
			return p
		pos=item_padding[:]
		l=[self.inactive_bg]*self.menu.position
		l.extend([self.active_bg])
		l.extend([self.inactive_bg]*(len(self.items)-1-self.menu.position))
		l=[(self.items[i],l[i]) for i in range(0,len(l))]
		for item,bg in l:
			pos=paint_item(item,bg,pos)

class Pokedex(ScreenItem):

	viewable=9*6

	def __init__(self,engine,card):
		ScreenItem.__init__(self,engine,engine.resolution)
		pkdx=self.engine.pkdx
		known_species=sorted([k for k in card['pokedex'].keys()],key=lambda sp:pkdx[sp]['id'])
		species_list=[None]*pkdx[known_species[-1]]['id']		
		for species in known_species:
			species_list[pkdx[species]['id']-1]=species
		self.menu=Menu(species_list)
		def create_item(species_name):
			return {
				'surf':self.engine.sprites['pokemon::icons::'+species_name],
				'key':species_name,
				'species':pkdx[species_name],
				'seen':card['pokedex'][species_name]
			}
		self.items=dict([(species,create_item(species)) for species in species_list if species])
		self.items[None]={'surf':self.engine.sprites['pokemon::icons::none']}
		self.menu.connect_signal('post-position-change',lambda ev:self.update())
		self.connect_signal(pygame.KEYDOWN,self.exit_gaurd,self.move_cursor)
		self.connect_signal('post-paint',self.paint_items)

	@EventHandler.Signal('paint')
	def update(self):self.fill((230,230,230))

	@property
	def viewable_items(self):
		items=self.menu.items
		l=len(items)
		p=self.menu.position
		m=Pokedex.viewable
		if l-p-m>=0:
			return items[p:p+m]
		else:
			return items[p:]+items[:m-(l-p)]

	def exit_gaurd(self,event):self.stop() if event.key==pygame.K_BACKSPACE else None

	def move_cursor(self,event):
		mappings={
			pygame.K_RIGHT:1,
			pygame.K_LEFT:-1,
			pygame.K_UP:-9,
			pygame.K_DOWN:9
		}
		if event.key in mappings:
			self.menu.position+=mappings[event.key]

	def paint_items(self):
		padding,default_pos=[8,8],[0,0]
		pos=default_pos[:]
		active_bg,inactive_bg,cursor_bg=pygame.Surface((48,48)),pygame.Surface((48,48)),pygame.Surface((48,48))
		cursor_bg.fill((200,200,250))
		active_bg.fill((200,100,140))
		inactive_bg.fill((200,200,200))
		once=False
		def paint_item(item,bg,pos):
			self.blit(bg,pos)
			self.blit(item['surf'],combine(pos,padding,lambda a,b:a+b))
		for item in self.viewable_items:
			data=self.items[item]
			bg=active_bg if data.get('seen',0)==1 else inactive_bg
			bg=bg if once else cursor_bg
			paint_item(data,bg,pos)
			w=max(bg.get_width(),data['surf'].get_width()+2*padding[0])
			pos[0]+=w
			if pos[0]+w>=self.get_width():
				pos[1]+=max(bg.get_height(),data['surf'].get_height()+2*padding[1])
				pos[0]=default_pos[0]
			once=True

class OverWorldMenu(ScreenItem):

	icon_rotate_range=range(0,30,1)
	icon_size=[32,32]
	icon_rotate_rate=5

	width=195
	border_width=5

	bg_color=(40,50,100,180)
	border_color=(240,245,255)
	
	menu_items=('pkdx','roster','bag','card','settings','cancel')

	def __init__(self,engine,save):
		Task.__init__(self,engine)
		height=self.engine.resolution[1]
		font,width=self.engine.main_font,self.engine.main_font.trim_width(self.width)
		def set_bg():
			w=self.width+self.border_width
			ScreenItem.__init__(self,engine,(w,height),pygame.SRCALPHA,(engine.resolution[0]-w,0))
			self.fill(self.bg_color,(self.border_width,0,self.width,height))
			pygame.draw.rect(self,self.border_color,(0,0,self.border_width,height))
		icon_source=([0,0] if save['card'].get('gender','m')=='m' else [self.icon_size[0],0])+self.icon_size
		def create_item(item):
			item_values={
				'pkdx':'Pokedex',
				'roster':'Roster',
				'bag':'Bag',
				'card':save['card']['name'],
				'settings':'Options',
				'cancel':'Cancel'
			}
			ico,a_ico=pygame.Surface(self.icon_size),pygame.Surface(self.icon_size)
			ico.blit(self.engine.sprites["menu::"+item],(0,0),icon_source)
			a_ico.blit(self.engine.sprites["menu::"+item],(0,0),combine(icon_source,[self.icon_size[0],0,0,0],lambda a,b:a+b))
			transparent(ico)
			transparent(a_ico)
			return {
				'surf':font.render_lines(width,item_values[item])[0],
				'icon':ico,
				'active_icon':a_ico,
				'val':item
			}
		set_bg()
		self.save=save
		self.items=[create_item(item) for item in self.menu_items]
		self.menu=Menu(self.menu_items)
		self.icon_rotation=AnimationFromGenerator(self.icon_rotate_rate,symmetrical(self.icon_rotate_range))
		self.connect_signal('frame',self.paint_items)
		self.connect_signal(pygame.KEYDOWN,self.handle_key)
		self.menu.connect_signal('post-select',self.respond)
		self.menu.connect_signal('position-change',lambda val:self.icon_rotation.reset())

	def handle_key(self,event):
		k=event.key
		if k==pygame.K_UP:self.menu.go_up()
		elif k==pygame.K_DOWN:self.menu.go_down()
		elif k==pygame.K_RETURN:self.menu.select()
		elif k==pygame.K_BACKSPACE:self.stop()

	def paint_items(self):
		pos=list(self.blit_position)
		pos[0]+=self.border_width
		for item in self.items:
			ico,s=None,item['surf']
			if item['val']==self.menu.items[self.menu.position]:
				if self.icon_rotation.complete:self.icon_rotation.reset()
				ico=pygame.transform.rotate(item['active_icon'],self.icon_rotation.value)
			else:ico=item['icon']
			self.engine.surf.blit(ico,pos)
			self.engine.surf.blit(s,combine(combine(pos,[ico.get_width(),5],lambda a,b:a+b),[0,ico.get_height()-s.get_height()],lambda a,b:a+(b//2)))
			pos[1]+=max([ico.get_height(),s.get_height()])

	@EventHandler.Signal('callback')
	def callback(self,t):
		t.connect_signal('stop',self.start)
		t.connect_signal('start',self.stop)
		t.start()

	def respond(self):
		response=self.menu.value
		if response=='cancel':
			self.stop()
		elif response=='roster':
			self.callback(RosterView(self.engine,self.save['card']))
		elif response=='card':
			self.callback(TrainerCardView(self.engine,self.save['card']))
		elif response=='bag':
			self.callback(BagView(self.engine,self.save['card']))
		elif response=='pkdx':
			self.callback(Pokedex(self.engine,self.save['card']))
		elif response=='settings':
			print("OPTIONSPAGE")

class FlashScreen(Task):

	def __init__(self,engine):
		Task.__init__(self,engine)
		self.color=[0]*3
		self.connect_signal('frame',self.update_color)
		self.connect_signal('frame',lambda:self.engine.surf.fill(self.color))

	def update_color(self):
		nc=self.color[0]+1
		if nc>=256:self.color=[0]*3
		else:self.color=[nc]*3

class SaveLoader(ThreadedTask):

	def __init__(self,engine):
		ThreadedTask.__init__(self,engine)
		self.saves,self.save=engine.saves,None
		self.connect_signal('frame',self.select_save)

	def select_save(self):
		saves=[]
		i=1
		for save in self.engine.saves.values():
			saves.append((i,save['card']['name'],save))
			i+=1
		def get_save(v):
			if isinstance(v,str):v=v.lower()
			for save in saves:
				if v==save[0] or v in save[1].lower():
					return save[2]
			return None
		def print_saves():print(*[("{:3}:\t{}".format(*save[:2])) for save in saves],sep='\n')
		def parse_int(s):
			try:return int(s)
			except:return s
		if not GLOBAL_DEBUG:#For now, I just want to auto-load the first save in the list.
			while True:
				print_saves()
				self.save=get_save(parse_int(input("> ")))
				if self.save:break
		else:self.save=get_save(1)
		self.stop()

class Tileset(ScreenItem):

	tile_width=16
	tile_height=16
	update_frequency=1000

	def __init__(self,engine,source,m_perm):
		ScreenItem.__init__(self,engine,(Tileset.tile_width,source.get_height()),frame=[0,0,Tileset.tile_width,source.get_height()])
		self.src,self.moves=source,m_perm
		self.connect_signal('paint',self.update_rect)
		self.disconnect_signal('frame',self.paint)#Don't paint the tileset onto the screen.
		self.timer=OwnedTimer(self,Tileset.update_frequency)
		self.timer.connect_signal('elapse',self.update)

	def update_rect(self):
		self.blit_frame[0]+=Tileset.tile_width#Advance the tile-frame via x-position
		if self.blit_frame[0]>=self.src.get_width():#If this is the last frame...
			self.blit_frame[0]=0#Reset to the first-frame.
		self.blit(self.src,(0,0),self.blit_frame)

	def paint_tile(self,target,tile,pos):
		target.blit(self,pos,[0,tile*Tileset.tile_height,Tileset.tile_width,Tileset.tile_height])

	def tile_permissions(self,tile):return self.moves[tile]

class NPC(ScreenItemCopy):

	frame_maps={
		'look down':0,
		'look up':1,
		'look left':2,
		'look right':-2,
		'walk down a':3,
		'walk down b':4,
		'walk up a':5,
		'walk up b':6,
		'walk left a':7,
		'walk left b':8,
		'walk right a':-7,
		'walk right b':-8,
	}

	dir_mappings={
		'left':(0,[-1,0]),
		'right':(0,[1,0]),
		'up':(1,[0,-1]),
		'down':(1,[0,1])
	}

	step_speed=100

	def __init__(self,engine,map_view,npc_class,gender='m'):
		spr=trykeys(engine.sprites,"NPC::"+npc_class,"NPC::"+npc_class+"::"+gender)
		ScreenItemCopy.__init__(self,engine,spr,frame=[0,0,spr.get_width(),spr.get_height()//9])
		w_adjustment = floor((map_view.tileset.tile_width-self.blit_frame[2])/2.0)
		h_adjustment = map_view.tileset.tile_height-self.blit_frame[3]
		self.default_adjustment=(w_adjustment,h_adjustment)
		self.adjustment=list(self.default_adjustment)
		self.map_view = map_view
		self.steps,self.flip,self.timer,self.npc_class=[],False,None,npc_class
		self.step_frame=0
		self.timer=OwnedTimer(self,self.step_speed)
		self.timer.connect_signal('elapse',self.play_step)

	def paint(self):
		self.engine.surf.blit(self,self.blit_pos,self.blit_frame,self.blit_flags)

	@EventHandler.Signal('set-frame')
	def set_frame(self,fr):
		if isinstance(fr,str):
			fr=NPC.frame_maps[fr]
		self.src_frame[1]=abs(fr*self.get_height())
		self.flip=fr<0

	@property
	def blit_pos(self):
		r=[self.blit_position[0]*self.map_view.tileset.tile_width,self.blit_position[1]*self.map_view.tileset.tile_height]
		r[0]+=self.adjustment[0]+self.map_view.blit_position[0]
		r[1]+=self.adjustment[1]+self.map_view.blit_position[1]
		return r

	@EventHandler.Signal('add-step')
	def move(self,dir):
		if not self.timer.running:
			self.timer.start()
		self.steps.append(dir)

	def play_step(self):
		if len(self.steps)>0:
			self.adjustment=combine(self.adjustment,self.dir_mappings[self.steps[0]][1],lambda a,b:a+b*4)
			self.step_frame+=1
			if self.step_frame>=4:
				self.advance_steps()

	@EventHandler.Signal('step-complete')
	def advance_steps(self):
		self.blit_position=combine(self.blit_position,self.dir_mappings[self.steps[0]][1],lambda a,b:a+b)
		self.adjustment=list(self.default_adjustment)
		self.steps=self.steps[1:]
		self.step_frame=0;
		if len(self.steps)<1:
			self.timer.stop()

class NPCManager(Task):

	update_frequency=500

	def __init__(self,engine,mapview):
		Task.__init__(self,engine)
		self.npcs=[]
		self.map_view=mapview
		self.timer=OwnedTimer(self,NPCManager.update_frequency)
		self.timer.connect_signal('elapse',self.move_npcs,self.paint_npcs)
		self.map_view.connect_signal('post-paint',self.paint_npcs)
		self.connect_signal('start',self.start_npcs)
		self.paused=False

	def start_npcs(self):
		for npc in self.npcs:
			if not npc.running:
				npc.start()

	def move_npcs(self):
		if self.paused:return
		for npc in self.npcs:
			if random.choice([True]*5 + [False]*2):
				npc.move(random.choice(['left','right','up','down']))

	def paint_npcs(self):
		for npc in self.npcs:
			npc.paint()

class Camera(Task):

	def __init__(self,engine,view_size,total_size):
		Task.__init__(self,engine)
		self.center_field=combine(total_size,(2,2),lambda a,b:a//b)
		self.__pos__=[0,0]
		self.center()

	def center(self):self.set_position(self.center_field[0],self.center_field[1])

	@property
	def position(self):return combine(self.center_field,self.__pos__,lambda a,b:a-b)

	def adjust(self,x,y):self.set_position(*combine(self.__pos__,[x,y],lambda a,b:a+b))

	@EventHandler.Signal('position-changed')
	def set_position(self,x,y):self.__pos__=[x,y]

class MapRenderer(ScreenItem):

	def __init__(self,engine,mapp):
		self.map_data=mapp
		size=self.map_data['size']
		self.width=size[0]
		self.height=size[1]
		self.layers=len(self.map_data['data'])
		self.tileset=Tileset(engine,engine.tilesets[mapp['tileset']],engine.tileset_data[mapp['tileset']])
		size=combine(size,[self.tileset.tile_width,self.tileset.tile_height],lambda a,b:a*b)
		ScreenItem.__init__(self,engine,size)
		self.connect_signal('start',self.tileset.start,lambda:self.camera.adjust(0,0))
		self.connect_signal('stop',self.tileset.stop)
		self.tileset.connect_signal('post-paint',self.update)
		self.connect_signal('paint',self.build_map)
		self.connect_signal(pygame.KEYDOWN,self.move_map)
		self.connect_signal('frame',self.paint_middle)
		self.camera=Camera(engine,engine.resolution,self.get_size())
		self.camera.connect_signal('post-position-changed',self.adjust_rect)

	def block_input(self):self.disconnect_signal(self.move_map)

	def open_input(self):self.connect_signal(pygame.KEYDOWN,self.move_map)

	def move_map(self,event):
		key_mappings={
			pygame.K_UP:[0,-16],
			pygame.K_DOWN:[0,16],
			pygame.K_RIGHT:[16,0],
			pygame.K_LEFT:[-16,0]
		}
		self.camera.adjust(*key_mappings.get(event.key,[0,0]))

	def adjust_rect(self,x,y):
		self.blit_position=self.camera.position[:]

	def paint_middle(self):
		s=pygame.Surface((16,16))
		s.fill((255,0,0))
		self.engine.surf.blit(s,self.camera.position)

	def build_map(self):
		tileset=self.tileset
		width_step=tileset.tile_width
		height_step=tileset.tile_height
		for layer in self.map_data['data']:
			pos=[0,0]
			for tile in layer:
				tileset.paint_tile(self,tile,pos)
				pos[0]+=width_step
				if pos[0]>=self.get_width():
					pos[1]+=height_step
					pos[0]=0

class OverWorld(Task):

	def __init__(self,engine,save):
		Task.__init__(self,engine)
		self.save=save
		self.menu=OverWorldMenu(self.engine,save)
		self.map_render=MapRenderer(self.engine,engine.maps['::'.join(self.save['location'][:-3])])
		self.npcs=NPCManager(engine,self.map_render)
		self.menu.connect_signal('callback',self.stop_bg)
		self.bg=FlashScreen(engine)
		link_tasks(self,self.bg,self.map_render,self.npcs)
		self.connect_signal(pygame.KEYDOWN,self.launch_menu)
		self.menu.connect_signal('start',lambda:self.map_render.block_input())
		self.menu.connect_signal('stop',lambda:self.map_render.open_input())
		self.npcs.npcs.append(NPC(engine,self.map_render,'ace'))

	def stop_bg(self,task):
		task.connect_signal('start',self.stop)
		task.connect_signal('stop',self.start)

	def launch_menu(self,event):
		if event.key==pygame.K_BACKSPACE and self.menu.running:
			self.menu.stop()
		elif event.key==pygame.K_RETURN and not self.menu.running:
			self.menu.start()

class TitleScreen(ScreenItemCopy):

	def __init__(self,engine):
		ScreenItemCopy.__init__(self,engine,engine.pictures['titlescreen'])
		self.save_loader=SaveLoader(self.engine)
		self.connect_signal('start',self.save_loader.start)
		self.connect_signal('paint',lambda:self.engine.surf.fill((0,0,0)))
		self.connect_signal('frame',self.launch_overworld)
	def launch_overworld(self):
		if not self.save_loader.running:
			OverWorld(self.engine,self.save_loader.save).start()
			self.stop()#m4n4phyPromo

class PkEngine(Engine):

	fonts=load_dict('fonts',load_font,open_mode='rb')
	sprites=Cache('sprites',load_trans_image,['rb'])
	tilesets=Cache('tilesets',pygame.image.load,['rb'])
	graphics=Cache('graphics',pygame.image.load,['rb'])
	pictures=Cache('images',pygame.image.load,['rb'])
	maps=Cache('maps',json.load)
	pkdx=Cache('species',json.load)
	items=Cache('items',json.load)
	saves=Cache('saves',json.load)
	info=Cache('info',json.load)
	tileset_data=Cache('tilesets data',json.load)
	npc_types=Cache('trainers',json.load)

	def __init__(self):
		Engine.__init__(self,resolution=(480,320))
		self.fonts = load_dict('fonts',load_font,open_mode='rb')
		self.main_font=self.fonts['fontWestern']
		self.textbox_font=self.fonts['dp-light']
		credits = ['PyPK!']
		if not GLOBAL_DEBUG:
			credits=['Python Pokemon!\nWritten by Full Metal','Built using pygame for Python 3.2 and 2.7']
		self.credits=[TextBox(self,c) for c in credits]
		self.connect_signal('start',self.launch_tasks)

	def launch_tasks(self):FlowControl(self,TitleScreen(self),*self.credits).start()

if __name__=="__main__":
	Runner(PkEngine())()